import Vue from 'vue';
import store from '@/store';
// import router from '@/router';

import UploadListDialogUi from './upload-list-dialog';
Vue.component(UploadListDialogUi.name, UploadListDialogUi);

import UploadListDialogUiI from './upload-list-dialog-copy';
const _UploadWindowCls = Vue.extend(UploadListDialogUiI);

// 打开上传列表窗口
const ShowUploadWindow = function () {
	if (document.getElementById('upload-window')) return;
	let instance = new _UploadWindowCls({ store,/* router */ });
	instance.$mount();
	document.body.appendChild(instance.$el);
	instance.visible = true;
}

export { ShowUploadWindow };

import UploadVerifyDialogUi from './upload-verify-dialog.vue';
const _UploadVerifyDialogCls = Vue.extend(UploadVerifyDialogUi);

/**
 * 打开上传验证窗口
 */
const _2CreateUploadVerifyDialog = function () {
	let instance = new _UploadVerifyDialogCls({ /*store, router*/ });
	instance.$mount();
	document.body.appendChild(instance.$el);
	return instance;
}


import CropUi from './crop-pic.vue';
const _CropUiCls = Vue.extend(CropUi);

const _2CreateCropWindow = function (info) {
	let instance = new _CropUiCls({ store, router });
	instance.$mount();
	instance.cropInfo = info;
	document.body.appendChild(instance.$el);
	return instance;
}

const state = {
	def: undefined,
	// 配置数据
	configs: {},
	// 上传对象数组
	infos: [],
	// 上传完成的数据将会存放在这里
	completions: {},
}

const getters = {
	/**
	 * 所有文件
	 * @returns 
	 */
	all({ infos }) {
		return [].concat(infos);
	},

	/**
	 * 过滤数据
	 */
	filter({ infos }) {
		/**
		 * @param {String} status 上传状态
		 * @param {String[]} keys UploadCfg 中的 key 集合
		 * @returns 状态 status 对应的数据。all 时返回所有数据；waiting 时返回 [waiting、paused] 对应数据；其它值时根据对应状态返回。
		 */
		return function (status, keys) {
			infos = infos || [];
			keys = keys || [];
			let arry = []
			if (!infos.length && !keys.length) return [];
			// 总的数组
			arry = infos
			// 如果keys不为空,过滤掉不满足条件的key
			if (keys.length > 0) {
				arry = (keys.length) ? arry.filter(item => {
					return keys.includes(item.key);
				}) : [];
			}
			// 根据不同的状态，返回不同的数组
			switch (status) {
				case 'all': return [].concat(arry);
				case 'waiting': return arry.filter(item => {
					return ['waiting', 'paused'].includes(item.status);
				});
				default: return arry.filter(item => {
					return item.status == status;
				});
			}
		}
	},

	/**
	 * 当前正在上传的对象
	 * @returns _Uploader
	 */
	current({ infos }) {
		/**
		 * 上传配置
		 * @param {String} key 上传配置中的 key
		 */
		return function (key) {
			return infos.find((loader) => {
				return (loader.status == 'uploading' && loader.key == key);
			})
		}
	},

	/**
	 * 是否已有对应 key 的 waiting(待上传) 或 uploading(正在上传) 的 loader
	 */
	has({ infos }) {
		/**
		 * @param {String} key 上传配置中的 key
		 */
		return function (key) {
			return infos.filter(item => {
				return (item.key == key) && ['uploading', 'waiting'].includes(item.status);
			}).length > 0;
		}
	},

	/**
	 * 是否已有侦听
	 */
	hasListener({ completions }) {
		/**
		 * @param {String} key 			上传配置中的 key
		 * @param {Function} complete 	上传完成回调函数
		 */
		return function (key, complete) {
			if (!key || !complete) return false;
			let array = completions[key] || [];
			return array.indexOf(complete) >= 0;
		}
	}
}

const mutations = {
	/**
	 * 注销配置
	 */
	unregister({ completions }, { cfg, complete }) {
		if (!cfg || !complete) return;
		let array = completions[cfg.key] || [];
		if (!array.length) return;
		let index = array.indexOf(complete);
		if (index < 0) return;
		array.splice(index, 1);
	},

	/**
	 * 注册回调方法
	 * @param {UploadCfg} 	cfg			上传配置
	 * @param {Function} 	complete 	上传完成回调
	 */
	__register__({ configs, completions }, { cfg, complete }) {
		if (!cfg || !complete) return;
		configs[cfg.key] = cfg;
		let array = completions[cfg.key] || [];
		if (array.indexOf(complete) >= 0) return;
		array.push(complete);
		completions[cfg.key] = array;
	},

	/**
	 * [ 内部调用 ]上传完成/成功 
	 * @param {_Uploader} loader
	 */
	__uploaded__({ configs, completions }, loader) {
		console.log("completions", completions);
		if (!loader) return;
		const { key } = loader;
		// 调用回调函数
		let array = completions[key] || [];
		for (let index = 0; index < array.length; index++) {
			const func = array[index];
			if (!func) continue;
			console.log("调用");
			func(loader.result);
		}

		const config = configs[key];
		if (!config || !config.multiple) {
			// 配置不存在或当前为单文件上传时，清空 complete 回调数组
			completions[key] = [];
			return
		}
	},

	/**
	 * [ 内部调用 ] 添加 loader 到上传队列中
	 */
	__add__(state, loader) {
		state.infos.push(loader);
	},

	/**
	 * [ 内部调用 ] 将 loader 从上传数据数组中移除。 loader 对象状态只能是 deleted
	 */
	__deleted__({ infos }, loader) {
		const { status } = loader;
		if (status != 'deleted') return;
		let index = infos.indexOf(loader);
		if (index < 0) return;
		infos.splice(index, 1);
	},
}

function _dragUploadFile(target, config) {
	return new Promise((resolve) => {
		_verifyUploadFile(target, config, resolve)
	})
}

/**
 * 
 * @param {Array} selectes filelist
 * @param {Object} config 配置信息
 * @returns 
 */
function _verifyUploadFile(selectes, config, resolve) {

	if (!selectes || !selectes.length) return resolve([]);
	// 创建验证文件
	let verifies = [];
	for (let index = 0; index < selectes.length; index++) {
		verifies.push(new VerifyFile(config, selectes[index]));
	}

	/**
	 * 不符合要求文件的数据
	 * @param {VerifyFile[]} files 
	 * @returns 数量
	 */
	function _count(files) {
		return files.filter(item => {
			return !item.useable;
		});
	}

	/**
	 * 弹出窗口进行确认
	 * @param {VerifyFile[]} verifies 
	 */
	function _dialog(verifies) {
		window['upload-verify-dialog'] = window['upload-verify-dialog'] || _2CreateUploadVerifyDialog();
		window['upload-verify-dialog'].show(verifies).then((files) => {
			resolve(files);
		}).catch(() => { });
	}

	switch (config.tip) {
		case 'auto':
			// 自动判断。
			if (_count(verifies) == 0) {
				// 当所选待上传文件中全部符合要求时，直接添加到上传队列中
				resolve(verifies);
			} else {
				// 若有不符合要求的文件，则显示确认窗口
				_dialog(verifies);
			}
			break;

		case 'always':
			// 总是显示。弹出确认窗口。
			_dialog(verifies);
			break;

		case 'none':
			// 总是不显示。过滤出可上传文件进入上传流程。
			resolve(verifies.filter(item => {
				return item.useable;
			}));
			break;
	}
}

/**
 * 选择并验证文件
 * @param {UploadCfg} config 上传配置
 * @returns Promise
 */
function _2SelectLocalFile(config) {
	// 异步选择
	return new Promise((resolve) => {
		const id = '__upload_file_selector';
		let input = document.getElementById(id);
		// 移除原组件
		if (input) document.body.removeChild(input);
		// 创建新组件并添加属性
		input = document.createElement('input');
		input.setAttribute('id', id);
		input.setAttribute('type', 'file');
		input.setAttribute('name', 'file');

		const { multiple, accept } = config;
		if (multiple) {
			input.setAttribute('multiple', 'multiple');
		}
		input.setAttribute('accept', accept);
		input.setAttribute("style", 'visibility:hidden');
		// 放置到舞台
		document.body.appendChild(input);
		// 添加事件
		input.addEventListener('change', ({ target }) => {
			const { files: selectes } = target || {};
			_verifyUploadFile(selectes, config, resolve)
			// if (!selectes || !selectes.length) return resolve([]);
			// // 创建验证文件
			// let verifies = [];
			// for (let index = 0; index < selectes.length; index++) {
			// 	verifies.push(new VerifyFile(config, selectes[index]));
			// }

			// /**
			//  * 不符合要求文件的数据
			//  * @param {VerifyFile[]} files 
			//  * @returns 数量
			//  */
			// function _count(files) {
			// 	return files.filter(item => {
			// 		return !item.useable;
			// 	});
			// }

			// /**
			//  * 弹出窗口进行确认
			//  * @param {VerifyFile[]} verifies 
			//  */
			// function _dialog(verifies) {
			// 	window['upload-verify-dialog'] = window['upload-verify-dialog'] || _2CreateUploadVerifyDialog();
			// 	window['upload-verify-dialog'].show(verifies).then((files) => {
			// 		resolve(files);
			// 	}).catch(() => { });
			// }

			// switch (config.tip) {
			// 	case 'auto':
			// 		// 自动判断。
			// 		if (_count(verifies) == 0) {
			// 			// 当所选待上传文件中全部符合要求时，直接添加到上传队列中
			// 			resolve(verifies);
			// 		} else {
			// 			// 若有不符合要求的文件，则显示确认窗口
			// 			_dialog(verifies);
			// 		}
			// 		break;

			// 	case 'always':
			// 		// 总是显示。弹出确认窗口。
			// 		_dialog(verifies);
			// 		break;

			// 	case 'none':
			// 		// 总是不显示。过滤出可上传文件进入上传流程。
			// 		resolve(verifies.filter(item => {
			// 			return item.useable;
			// 		}));
			// 		break;
			// }
		});
		// 执行点击事件
		input.click();
	});
}

/**
 * 验证 cfg(UploadCfg) 数据，若不存在，则使用 state 中的默认数据，若 state 也不存在该数据则抛出错误
 * @param {Object} state store 中的 state 数据
 * @param {UploadCfg} cfg 当前配置 
 * @returns UploadCfg
 */
function _2VerifyCfg(state, cfg) {
	// 验证上传配置 cfg(UploadCfg)
	if (!cfg) {
		// 当参数 cfg(UploadCfg) 为空值时，则使用 state 中的 def(默认配置) 为基础配置
		cfg = state.def;
	}

	if (!cfg) {
		// 当上传配置不存在时，抛出错误
		throw new Error(`无上传配置数据，您可以在调用 startup 时传入参数 { cfg: UploadCfg }，或者可以在注册上传组件(UploadJs) 时添加默认的配置数据, Vue.use(UploadJs, UploadCfg`);
	}

	return cfg;
}

const actions = {

	/**
	 * 上传文件(store 内部调用)。根据队列情况进行查询上传文件
	 * @param {String} 		key 	上传配置中的 key
	 * @param {_Uploader} 	prev 	上一个已完成的上传对象
	 */
	__next__({ state: { infos }, dispatch, getters }, { key, prev }) {
		// 当前有正在执行的上传对象 > 返回
		if (getters.current(key)) return;
		// 无可上传数据 > 返回
		if (!infos.length) return;
		// 从队列中取得待上传对象
		let index = infos.indexOf(prev) || -1;
		// 根据 index 值整理 infos 数组顺序
		let array = (index <= 0) ? [].concat(infos) : infos.slice(index + 1).concat(infos.slice(0, index + 1));
		// 查询索引 index 的下一个 waiting 状态的上传对象
		let loader = array.find((item) => {
			return item.status == 'waiting';
		})
		// 无可上传对象 > 返回
		if (!loader) {
			console.log('没有可上传文件了');
			return;
		}
		console.log("--------->>>执行上传>>>", loader);
		// 执行上传
		dispatch('upload', loader);
	},

	/**
	 * 执行上传
	 * @param {_Uploader} loader 当前需要上传的对象
	 */
	upload({ getters, dispatch, commit }, loader) {

		if (getters.current(loader.key)) {
			// 若已有正在上传的文件，则将当前 loader 对象的状态更改为 waiting 待传
			loader.status = 'waiting';
			return;
		}
		// console.log(`开始上传文件[ ${loader.name}]`);
		// 开始上传
		loader.startup(() => {
			if (['success', 'failed'].includes(loader.status)) {
				// console.log(`文件[ ${loader.name} ]上传${{ success: '成功', failed: '失败' }[loader.status]}`);
				// 文件上传成功/失败，开始查找下一个 loader 对象并进行上传
				commit('__uploaded__', loader);
				console.log(loader, "上次完成");
				dispatch('__next__', { key: loader.key, prev: loader });
			} else if ('deleted' == loader.status) {
				console.log(`[不做处理] 文件[ ${loader.name} ]已删除`);
			} else if ('paused' == loader.status) {
				console.log(`[不做处理] 文件[ ${loader.name} ]已暂停上传`);
			}
		});
	},

	/**
	 * 注册配置
	 * @param {UploadCfg} 	cfg			上传配置
	 * @param {Function}	complete 	上传完成的回调
	 */
	register({ getters, commit }, { cfg, complete }) {
		return new Promise((resolve, reject) => {
			// 数据不全时，拒绝注册
			if (!cfg || !complete) return reject();
			// 如果是单文件上传，查看有没有对应的侦听器
			if (!cfg.multiple && getters.hasListener(cfg.key)) {
				// 单文件上传且已有侦听回调，则拒绝注册
				return reject();
			}
			// 调用注册方法
			console.log("注册配置");
			commit('__register__', { cfg, complete });
			resolve();
		});
	},

	/**
	 * 添加需要上传的file文件
	 * 
	 * @param {UploadCfg} 	cfg		[可空] 上传配置内容。若为空对象时，会直接读取默认配置进行使用。
	 * @param {Object}		params 	参数（上传文件时，某些功能可能需要添加一些额外的参数。如：上传文件到指定文件夹时需要添加文件夹 ID，extra 为 { dirId: 5 } ）  
	 * @param {Array}		files   需要添加的file文件列表
	 * @returns 
	 */
	add({ state, dispatch, commit }, { cfg, params, files } = {}) {

		// 验证 cfg
		cfg = _2VerifyCfg(state, cfg);
		// 遍历文件转换为上传数据存储到上传队列中
		for (let index = 0; index < files.length; index++) {
			// 添加到内存中
			commit('__add__', new _Uploader(cfg, new VerifyFile(cfg, files[index]), params));
		}
		// 开始上传下一个文件
		dispatch('__next__', { key: cfg.key })

	},


	/**
	 * 选择并上传文件（添加到上传队列）
	 * @param {UploadCfg} 	cfg		[可空] 上传配置内容。若为空对象时，会直接读取默认配置进行使用。
	 * @param {Object}		params 	参数（上传文件时，某些功能可能需要添加一些额外的参数。如：上传文件到指定文件夹时需要添加文件夹 ID，extra 为 { dirId: 5 } ） 
	 */
	startup({ state, getters, dispatch, commit }, { cfg, params } = {}) {
		// 验证 cfg
		cfg = _2VerifyCfg(state, cfg);

		if (!cfg.multiple) {
			// 单文件上传时，同时只能存在一个相同key的上传对象
			if (getters.has(cfg.key)) {
				console.log('重复了');
				return;
			}
		}
		// 选择本地文件
		_2SelectLocalFile(cfg).then((files) => {
			if (cfg.crop) {
				// _2CreateCropWindow(cfg.crop).show2(files[0].file)
				let verify = files[0]
				// 提交文件到服务器
				_2CreateCropWindow(cfg.crop).show2(verify.file).then((file) => {
					// 添加到内存中
					verify.file = file
					commit('__add__', new _Uploader(cfg, verify, params));
					// 开始上传下一个文件
					dispatch('__next__', { key: cfg.key })
					// console.log(file);
				}).catch(() => { });
				return;
			}
			// 遍历文件转换为上传数据存储到上传队列中
			for (let index = 0; index < files.length; index++) {
				// 添加到内存中
				commit('__add__', new _Uploader(cfg, files[index], params));
			}
			// 开始上传下一个文件
			dispatch('__next__', { key: cfg.key })
		}).catch((error) => {
			console.log(error);
		});
	},


	/**
	 * 拖动文件上传 （添加到文件队列）
	 * @param {UploadCfg} 	cfg		[可空] 上传配置内容。若为空对象时，会直接读取默认配置进行使用。
	 * @param {Object}		params 	参数（上传文件时，某些功能可能需要添加一些额外的参数。如：上传文件到指定文件夹时需要添加文件夹 ID，extra 为 { dirId: 5 } ）
	 */
	dragStartUp({ state, getters, dispatch, commit }, { files: selectes, cfg, params } = {}) {
		cfg = _2VerifyCfg(state, cfg);

		if (!cfg.multiple) {
			// 单文件上传时，同时只能存在一个相同key的上传对象
			if (getters.has(cfg.key)) {
				return;
			}
		}
		// 选择本地文件
		_dragUploadFile(selectes, cfg).then((files) => {
			// 遍历文件转换为上传数据存储到上传队列中
			for (let index = 0; index < files.length; index++) {
				// 添加到内存中
				commit('__add__', new _Uploader(cfg, files[index], params));
			}
			// 开始上传下一个文件
			dispatch('__next__', { key: cfg.key })
		}).catch(() => { });
	},
	/**
	 * 清除
	 * 当 loader 牌暂停状态下时，该方法可以清除服务已上传的数据同时删除上传任务
	 * @param {_Uploader} 	loader 上传对象
	 */
	delete({ commit }, loader) {
		if (!loader || !loader.deletable) return;
		const { form } = loader;
		// 将 loader 状态更改为 deleted（删除中）
		loader.status = 'deleted';
		// 在 mutations 中将 loader 对象移除
		commit('__deleted__', loader);
		// console.log(`删除上传任务: [ ${form.fileName}(状态 > ${status}) ]`);
		// 调用删除接口// 不管成功或失败都将数据从内存中移除
		if (!this.del) return;
		$http.delete(this.del, form).then(() => { }).catch(() => { });
	},

	/**
	 * 暂停上传。仅 status 为 uploading 及 waiting 时可执行暂停操作
	 * 1、status == 'uploading' > 将状态设置为 pausing(正在暂停)，后续过程由 loader 在上传中进行处理。
	 * 2、status == 'waiting' > 将状态设置为 paused(已暂停)
	 * @param {_Uploader} target 待取消对象
	 */
	pause(_, loader) {
		if (!loader || !loader.pausable) return;
		// 根据 status 判断当前状态
		const { status } = loader;
		switch (status) {
			case 'uploading':
				// 正在上传时，将状态设置为 pausing(暂停中)
				loader.status = 'pausing';
				break;

			case 'waiting':
				// 待传时，将状态修改为 paused
				loader.status = 'paused';
				break;
		}
	},
}

export default {
	install(V, cfg) {
		if (store.hasModule('upload-store')) return;
		state.def = cfg;
		store.registerModule('upload-store', {
			namespaced: true,
			state, getters, mutations, actions
		})
	}
}

import $http from '@http/index.js';

/**
 * 文件上传
 */
class _Uploader {

	/**
	 * 当前对象是否可删除(是否需要显示[ 删除 ]按钮)
	 */
	get deletable() {
		return ['failed', 'success', 'paused'].includes(this.status);
	}

	/**
	 * 当前对象是否可暂停(是否需要显示[ 暂停 ]按钮)
	 */
	get pausable() {
		return ['uploading', 'waiting'].includes(this.status);
	}

	/**
	 * 文件最后修改时间
	 */
	get lastModify() {
		return this.file.lastModified;
	}

	/**
	 * 文件大小
	 */
	get size() {
		return this.file.size;
	}

	/**
	 * 文件上传进度
	 */
	get progress() {
		if (this.size <= 0) return 0;
		return Math.floor((this.uploaded / this.size) * 100);
	}

	/**
	 * 构造函数
	 * @param {UploadCfg} 	config 			上传配置
	 * @param {VerifyFile} 	data			验证完成的文件对象
	 * @param {Object}		extra			需要携带的额外的参数
	 */
	constructor(config, data, extra) {
		const { name, suffix, file } = data;
		const fileName = `${name}${suffix}`;
		const { lastModified: lastModify, size: fileSize } = file;
		const id = `${fileName}-${fileSize}-${lastModify}-${Date.now()}`;

		// 从配置中获取相关数据
		const { key, server, desc, temp, del, method, params, piece, name: fileKey } = (config || {});

		// 上传是需要携带的表单数据
		const form = Object.assign({ fileType: FileUtil.type(file), fileName, fileSize, lastModify }, extra || {}, params || {});

		// 设置数据
		Object.assign(this, { key, name: fileName, desc, server, temp, del, method, id, file, fileKey, status: 'waiting', uploaded: 0 });
		// 表单数据
		this.form = Object.assign({}, form || {});
		// 设置裁片大小
		this.piece = piece || this.file.size;
	}

	/**
	 * 开始上传当前文件
	 * @param {Function} update 更新方法，当上传过程中出现数据变化时，调用该方法
	 */
	startup(update) {
		// 续传验证
		this.status = 'uploading';
		// 查看服务端缓存数据（续传检查）
		if (this.temp) {
			console.log("开始上传", this.temp);
			$http.get(this.temp, this.form).then(uploaded => {
				// 更新上传进度
				this.uploaded = uploaded || 0;
				// 开始上传
				this.running(update);
			}).catch(() => {
				this.status = 'failed';
				update();
			});
		} else {
			this.uploaded = 0;
			this.running(update);
		}
	}

	/**
	 * 上传。轮循调用服务端方法进行分片上传，若不分片，则直接上传整个文件
	 * @param {Function} update 更新方法，当上传过程中出现数据变化时，调用该方法
	 */
	async running(update) {
		let { size } = this.file;
		// 计算裁片文件的 start(开始) 及 end(结束) 位置
		let start = this.uploaded;
		let end = Math.min(start + this.piece, size);
		// 裁片
		let piece = this.file.slice(start, end);
		// 合并参数
		let data = Object.assign({}, this.form, {
			startSize: start,
			[this.fileKey]: piece
		});

		// 合成 form 表单
		const form = new FormData();
		Object.keys(data).forEach(key => {
			form.append(key, data[key]);
		});

		try {
			if (this.status !== 'uploading') {
				update();
				return;
			}
			// 请求接口
			let result = undefined;
			if (this.method == 'post') {
				result = await $http.post(this.server, form, { 'Content-Type': 'multipart/form-data' });
			} else if (this.method == 'put') {
				result = await $http.put(this.server, form, { 'Content-Type': 'multipart/form-data' });
			} else {
				result = await $http.get(this.server, form, { 'Content-Type': 'multipart/form-data' });
			}

			let { startSize } = result;

			this.uploaded = startSize;
			this.result = result;

			if (startSize >= this.size) {
				// 上传完成
				this.status = 'success';
				update();
				return;
			}

			// 检测到 pausing 状态时，将状态更新为 paused
			if (this.status == 'pausing') {
				this.status = 'paused';
				update();
				return;
			}

			// 继续上传下一片段
			this.running(update);
		} catch (error) {
			console.log('出错球', error);
			// 服务器错误// 更新状态
			this.status = 'failed';
			update();
		}
	}
}

/**
 * 上传配置
 */
class UploadCfg {

	/**
	 * 用于 input 组件上的文件类型限制
	 */
	get accept() {
		// console.log((this.accepts || []).join(','));
		return (this.accepts || []).join(',');
	}

	/**
	 * 裁片大小
	 */
	get piece() {
		const slice = this.slice;
		if (slice === undefined || typeof slice != 'number') return undefined;
		return Math.floor(slice * 1024 * 1024);
	}

	/**
	 * 构造函数
	 * @param {String} 	key				[非空] 配置的唯一值（不同页面/功能在上传文件所需参数、地址配置会出现不同，在存储这些配置时需要有一个唯一值作为key，该值不可为 def，def值必须作为通用配置[ .src/core/ui/index.js中配置 ]时使用）。
	 * @param {String}	server			[非空] 上传地址。
	 * @param {String}	temp			[ 可空 ] 续传检查地址。
	 * @param {String}	del				[ 可空 ] 临时文件删除地址。
	 * @param {Boolean} auto 			[ 可空 ] 是否开启自动上传（默认 false，不开启）。
	 * @param {Boolean} multiple		[ 可空 ] 是否支持多文件上传（默认 false，不支持）。
	 * @param {Boolean} drag			[ 功能未开放 ]是否允许拖拽上传（默认 false，不允许）。
	 * @param {Number}  slice			[ 可空 ] 分片大小（单位: M，默认 2，若设置为 undefined 或 0 时不做分片）。
	 * @param {String}	method			[ 可空 ] 上传请求方式（默认 'post', 包含: 'get', 'put', 'post'）。
	 * @param {String}	name			[ 可空 ] 上传文件时文件对象在上传过程中的参数名（默认 'file'）。
	 * @param {Object}	headers			[ 可空 ] 上传文件时需要设置的通用头数据（默认 undefined）。
	 * @param {Object}	params			[ 可空 ] 上传文件时需要携带的通用参数（默认 undefined）。
	 * @param {String[]}accepts			[ 可空 ] 上传文件后缀限制。如：['.jpeg', '.jpe', '.mpe']。
	 * @param {String[]}types			[ 可空 ] 上传文件类型限制。包含: 'image' > 图片文件, 'video' > 视频文件, 'audio' > 音频文件, 'word' > 文档文件, 'all' > 所有类型文件；当数组长度大于 1 时，数组中的 'all' 字符串将会被忽略。若 types 与 accepts 同时存在，则将两个参数所包含的类型进行合并。若 types 与 accepts 均为空值时，文件类型不做限制。
	 * @param {Number}	limitSize 		[ 可空 ] 上传文件大小限制。待上传文件大小若超过该值将会被拒绝上传。
	 * @param {Number}	limitNameLen 	[ 可空 ] 上传文件的文件名长度限制。待上传文件的文件名长度超过该值将会被拒绝上传。
	 * @param {String}	tip				[ 可空 ] 错误提示类型，默认为 auto。包含：1、auto > 自动判断。（判断规则：当所选待上传文件中全部符合要求时，直接添加到上传队列中；若有不符合要求[ 后缀、文件大小、文件名长度等 ]的文件时，则显示窗口。）；2、always > 总是显示；3、none -> 总是不显示（不推荐）。
	 * @param {Object} crop				[ 可空 ] 裁剪配置。默认 undefined，不做裁剪。
	 */
	constructor(key, { server, temp, del }, { desc, auto, multiple, drag, slice, method, name, headers, params, accepts, types, limitSize, limitNameLen, tip, crop } = {}) {
		if (!server || !key) throw new Error('UploadCfg::constructor server 为必要参数，不可为空值 >_<');

		const def = { desc: '资料', temp: undefined, auto: false, multiple: false, drag: false, slice: 2, method: 'post', name: 'file', headers: {}, params: {}, accepts: undefined, types: undefined, limitSize: undefined, limitNameLen: undefined, tip: 'auto', crop: undefined };


		Object.assign(this, def, { key, server });
		// 若指定参数存在则设置到对象中，覆盖默认值
		if (desc !== undefined) this.desc = desc;
		if (temp !== undefined) this.temp = temp;
		if (del !== undefined) this.del = del;
		if (auto !== undefined) this.auto = auto;
		if (multiple !== undefined) this.multiple = multiple;
		if (drag !== undefined) this.drag = drag;
		if (slice !== undefined) this.slice = slice;
		if (method !== undefined) this.method = method.toLowerCase();
		if (name !== undefined) this.name = name;
		if (headers !== undefined) this.headers = Object.assign({}, headers);
		if (params !== undefined) this.params = Object.assign({}, params);
		if (limitSize !== undefined) this.limitSize = limitSize;
		if (limitNameLen !== undefined) this.limitNameLen = limitNameLen;
		if (crop !== undefined) this.crop = Object.assign({}, crop);
		// 提示设置
		if (['auto', 'always', 'none'].includes(tip)) this.tip = tip;
		// 确认可上传文件
		types = FileUtil.accept(types || []);
		accepts = accepts || [];
		let array = types.concat(accepts.filter(suffix => {
			return !types.includes(suffix);
		}));
		this.accepts = (array.length) ? array : FileUtil.accept(['all']);
		// console.log('UploadCfg', this.key, this.accepts);
	}

	/**
	 * 文件类型是否没问题
	 * @param {File} file 
	 * @returns 
	 */
	isTypeOk(file) {

		//判断后缀与要求的文件类型是否一致，注意参数的位置file, this.accepts
		return FileUtil.isAccept(file, this.accepts);
	}

	/**
	 * 文件大小是否没问题
	 * @param {Number} size 
	 * @returns 
	 */
	isSizeOk(size) {
		if (!this.limitSize) return true;
		if (size <= 0) return false;
		return size <= this.limitSize * 1024 * 1024;
	}

	/**
	 * 文件名特殊字符是否没问题
	 * @returns 
	 */
	isSpecialCharOk(name) {
		return !(new RegExp(`[:|?*<>"]`).exec(name));
	}

	/**
	 * 文件名长度是否没问题
	 */
	isFileNameLenOk(name) {
		if (!this.limitNameLen) return true;
		return name.length <= this.limitNameLen;
	}
}

import FileUtil from './file.js';

/**
 * 验证文件。提供多种验证方式（动态验证）
 */
class VerifyFile {

	/**
	 * 是否符合上传要求
	 */
	get useable() {
		return this.isTypeOk && this.isSizeOk && this.isSpecialCharOk && this.isFileNameLenOk;
	}

	/**
	 * 文件类型是否没问题
	 */
	get isTypeOk() {
		return this.config.isTypeOk(this.file);
	}

	/**
	 * 文件大小是否没问题
	 */
	get isSizeOk() {
		return this.config.isSizeOk(this.file.size);
	}

	/**
	 * 文件名特殊字符是否没问题
	 */
	get isSpecialCharOk() {
		return this.config.isSpecialCharOk(this.name);
	}

	/**
	 * 文件名长度是否没问题
	 */
	get isFileNameLenOk() {
		return this.config.isFileNameLenOk(this.name);
	}

	/**
	 * 构造函数
	 * @param {UploadCfg} config 
	 * @param {File} file 
	 */
	constructor(config, file) {
		let { name } = file, { desc } = config;
		let suffix = `.${FileUtil.suffix(file)}`;
		name = name.slice(0, name.lastIndexOf(suffix));

		Object.assign(this, { name, desc, file, config, suffix });
	}
}

export { UploadCfg, VerifyFile };
